package com.alien.admin.modules.system.service.impl;

import com.alibaba.fastjson.JSON;
import com.alien.admin.config.FileProperties;
import com.alien.admin.exception.EntityExistException;
import com.alien.admin.exception.EntityNotFoundException;
import com.alien.admin.modules.deploy.domain.*;
import com.alien.admin.modules.deploy.repository.DpServerRepository;
import com.alien.admin.modules.deploy.repository.DpUserServerRepository;
import com.alien.admin.modules.deploy.service.DpEnvVariablesService;
import com.alien.admin.modules.deploy.service.DpK8sInstanceService;
import com.alien.admin.modules.deploy.service.DpServerConfigService;
import com.alien.admin.modules.deploy.service.dto.DpEnvVariablesDto;
import com.alien.admin.modules.deploy.service.dto.DpK8sInstanceDto;
import com.alien.admin.modules.deploy.service.dto.DpServerConfigDto;
import com.alien.admin.modules.gitlab.service.utils.GitLabServiceUtils;
import com.alien.admin.modules.security.service.OnlineUserService;
import com.alien.admin.modules.security.service.UserCacheClean;
import com.alien.admin.modules.system.domain.User;
import com.alien.admin.modules.system.repository.UserRepository;
import com.alien.admin.modules.system.service.DeptService;
import com.alien.admin.modules.system.service.UserService;
import com.alien.admin.modules.system.service.dto.*;
import com.alien.admin.modules.system.service.mapstruct.UserMapper;
import com.alien.admin.modules.utils.YamlBusinessService;
import com.alien.admin.modules.utils.YamlCicdTool;
import com.alien.admin.utils.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.models.Project;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.File;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: alien.guo
 * @date: 2020/12/24
 */
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "user")
@Slf4j
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final UserMapper userMapper;
    private final FileProperties properties;
    private final RedisUtils redisUtils;
    private final UserCacheClean userCacheClean;
    private final OnlineUserService onlineUserService;
    private final DeptService deptService;

    private final DpServerRepository dpServerRepository;
    private final DpUserServerRepository dpUserServerRepository;

    private final GitLabServiceUtils gitLabServiceUtils;

    private final DpServerConfigService dpServerConfigService;
    private final DpEnvVariablesService dpEnvVariablesService;

    private final DpK8sInstanceService dpK8sInstanceService;

    @Override
    public Object queryAll(UserQueryCriteria criteria, Pageable pageable) {
        Page<User> page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
        return PageUtil.toPage(page.map(userMapper::toDto));
    }

    @Override
    public List<UserDto> queryAll(UserQueryCriteria criteria) {
        List<User> users = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder));
        return userMapper.toDto(users);
    }

    @Override
    public List<UserDto> findByRoleId(Long roleId) {
        // 获取当前用户的所属部门和全部上级部门
        Long userId = SecurityUtils.getCurrentUserId();
        User user = userRepository.findById(userId).orElseGet(User::new);
        ValidationUtil.isNull(user.getId(), "User", "id", userId);

        DeptDto deptDto = deptService.findById(user.getDept().getId());
        List<DeptDto> depts = deptService.getSuperior(deptDto, new ArrayList<>());
        List<Long> deptIds = depts.stream().map(item -> item.getId()).collect(Collectors.toList());

        List<User> users = userRepository.findByRoleId(roleId);

        // 过滤有在上级或者本部门的该角色人员列表
        users = users.stream().filter(userItem -> {
            Long deptId = userItem.getDept().getId();
            int index = deptIds.indexOf(deptId);
            return index != -1;
        }).collect(Collectors.toList());
        return userMapper.toDto(users);
    }

    @Override
    @Cacheable(key = "'id:' + #p0")
    @Transactional(rollbackFor = Exception.class)
    public UserDto findById(long id) {
        User user = userRepository.findById(id).orElseGet(User::new);
        ValidationUtil.isNull(user.getId(), "User", "id", id);
        return userMapper.toDto(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(User resources) {
        if (userRepository.findByUsername(resources.getUsername()) != null) {
            throw new EntityExistException(User.class, "username", resources.getUsername());
        }
        if (userRepository.findByEmail(resources.getEmail()) != null) {
            throw new EntityExistException(User.class, "email", resources.getEmail());
        }
        if (userRepository.findByPhone(resources.getPhone()) != null) {
            throw new EntityExistException(User.class, "phone", resources.getPhone());
        }
        userRepository.save(resources);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(User resources) {
        User user = userRepository.findById(resources.getId()).orElseGet(User::new);
        ValidationUtil.isNull(user.getId(), "User", "id", resources.getId());
        User user1 = userRepository.findByUsername(resources.getUsername());
        User user2 = userRepository.findByEmail(resources.getEmail());
        User user3 = userRepository.findByPhone(resources.getPhone());
        if (user1 != null && !user.getId().equals(user1.getId())) {
            throw new EntityExistException(User.class, "username", resources.getUsername());
        }
        if (user2 != null && !user.getId().equals(user2.getId())) {
            throw new EntityExistException(User.class, "email", resources.getEmail());
        }
        if (user3 != null && !user.getId().equals(user3.getId())) {
            throw new EntityExistException(User.class, "phone", resources.getPhone());
        }
        // 如果用户的角色改变
        if (!resources.getRoles().equals(user.getRoles())) {
            redisUtils.del(CacheKey.DATE_USER + resources.getId());
            redisUtils.del(CacheKey.MENU_USER + resources.getId());
            redisUtils.del(CacheKey.ROLE_AUTH + resources.getId());
        }
        // 如果用户被禁用，则清除用户登录信息
        if (!resources.getEnabled()) {
            onlineUserService.kickOutForUsername(resources.getUsername());
        }
        user.setUsername(resources.getUsername());
        user.setEmail(resources.getEmail());
        user.setEnabled(resources.getEnabled());
        user.setRoles(resources.getRoles());
        user.setDept(resources.getDept());
        user.setJobs(resources.getJobs());
        user.setPhone(resources.getPhone());
        user.setNickName(resources.getNickName());
        user.setGender(resources.getGender());
        userRepository.save(user);
        // 清除缓存
        delCaches(user.getId(), user.getUsername());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCenter(User resources) {
        User user = userRepository.findById(resources.getId()).orElseGet(User::new);
        User user1 = userRepository.findByPhone(resources.getPhone());
        if (user1 != null && !user.getId().equals(user1.getId())) {
            throw new EntityExistException(User.class, "phone", resources.getPhone());
        }
        user.setNickName(resources.getNickName());
        user.setPhone(resources.getPhone());
        user.setGender(resources.getGender());
        userRepository.save(user);
        // 清理缓存
        delCaches(user.getId(), user.getUsername());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Set<Long> ids) {
        for (Long id : ids) {
            // 清理缓存
            UserDto user = findById(id);
            delCaches(user.getId(), user.getUsername());
        }
        userRepository.deleteAllByIdIn(ids);
    }

    @Override
    public UserDto findByName(String userName) {
        User user = userRepository.findByUsername(userName);
        if (user == null) {
            throw new EntityNotFoundException(User.class, "name", userName);
        } else {
            return userMapper.toDto(user);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePass(String username, String pass) {
        userRepository.updatePass(username, pass, new Date());
        flushCache(username);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateGitlabToken(String username, String gitlabToken) {
        userRepository.updateGitlabToken(username, gitlabToken);
        flushCache(username);
    }

    /**
     * 从gitlab同步项目信息
     */
    @Override
    public void syncProjectInfo() {

        User user = userRepository.findByUsername(SecurityUtils.getCurrentUsername());
        String gitlabToken = user.getGitlabToken();
        Long userId = user.getId();
        if (StringUtils.isBlank(gitlabToken)) {
            log.error("gitlabToken is blank");
            return;
        }

        List<Project> projectList = null;
        try {
            projectList = gitLabServiceUtils.getAllProject(gitlabToken);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (GitLabApiException e) {
            e.printStackTrace();
        }

        if (CollectionUtils.isEmpty(projectList)) {
            log.info("projectListIsEmpty");
            return;
        }

        // 更新服务列表
        for (Project project : projectList) {
            syncOneProject(project);
        }
        log.info("update dpServer finish");

        // 更新用户对应服务列表
        dpUserServerRepository.deleteByUserId(userId);
        for (Project project : projectList) {
            DpUserServer tmp = new DpUserServer();
            tmp.setProjectId(Long.valueOf(project.getId()));
            tmp.setUserId(userId);
            tmp.setCreateTime(new Timestamp(System.currentTimeMillis()));
            dpUserServerRepository.save(tmp);
        }
        log.info("update dpUserServer finish");
    }

    /**
     * 同步一个项目的信息
     *
     * @param project
     */
    public void syncOneProject(Project project) {
        // stepOne: 同步项目信息
        saveOrUpdateOneProject(project);

        // stepTwo-A: 同步项目在每个环境下的配置信息
        // 处理buildConfig，deployConfig, k8s实例配置信息，也就是项目对应的DpServerConfigDto对象
        // 一个环境对应一个DpServerConfigDto
        processDpServerConfigDtoWithEnv(project, "debug");
        processDpServerConfigDtoWithEnv(project, "test");
        processDpServerConfigDtoWithEnv(project, "huawei_test");

        // stepTwo-B: 同步项目在每个环境下的特有的环境变量
        // 比如对于tomcat项目，一定需要javaOpt这个参数
        processDpEnvVariablesDtoWithEnv(project, "debug");
        processDpEnvVariablesDtoWithEnv(project, "test");
    }

    /**
     * 保存一个项目的信息
     *
     * @param project
     */
    public void saveOrUpdateOneProject(Project project) {
        Optional<DpServer> dpServerOptional = dpServerRepository.findById(Long.valueOf(project.getId()));
        if (dpServerOptional.isPresent()) {
            DpServer dpServerFromDb = dpServerOptional.get();
            initDpServerFields(dpServerFromDb, project);
            dpServerRepository.saveAndFlush(dpServerFromDb);
        } else {
            DpServer tmpDpServer = new DpServer();
            tmpDpServer.setProjectId(Long.valueOf(project.getId()));
            tmpDpServer.setCreateTime(new Timestamp(System.currentTimeMillis()));
            initDpServerFields(tmpDpServer, project);
            dpServerRepository.saveAndFlush(tmpDpServer);
        }
    }

    /**
     * 初始DpServer对象的字段值
     *
     * @param tmpDpServer
     * @param project
     */
    public void initDpServerFields(DpServer tmpDpServer, Project project) {
        tmpDpServer.setName(project.getName());
        String codeTypeFromYaml = YamlBusinessService.getCodeTypeByProjectName(project.getName(), null);
        // 从Yaml配置文件中出来，只要不是空的，就是对的！
        if (StringUtils.isNotBlank(codeTypeFromYaml)) {
            tmpDpServer.setCodeType(codeTypeFromYaml);
        }
        tmpDpServer.setHttpUrl(project.getHttpUrlToRepo());
        tmpDpServer.setSshUrl(project.getSshUrlToRepo());
        tmpDpServer.setDescription(project.getDescription());
        tmpDpServer.setUpdateTime(new Timestamp(System.currentTimeMillis()));
    }

    /**
     * 处理某个项目的某个环境下的DpServerConfigDto记录。
     * 如果DpServerConfigDto记录存在，那么不处理；
     * 否则增加一个默认值
     *
     * @param project
     * @param env
     */
    public void processDpServerConfigDtoWithEnv(Project project, String env) {
        DpServerConfigDto dpServerConfigDtoDebug = dpServerConfigService.findFirstByProjectIdAndEnv(project.getId(), env);
        if (dpServerConfigDtoDebug == null || dpServerConfigDtoDebug.getId() == null) {

            DpServerConfig resourceNew = new DpServerConfig();
            resourceNew.setProjectId(project.getId());
            resourceNew.setEnv(env);

            // 华为云使用内部test环境的配置
            String fromEnv = env;
            if("huawei_test".equals(env)) {
                fromEnv = "test";
            }

            // deployParam
            ServiceDeployParam po = YamlCicdTool.getDefaultDeploymentForOneProject(project.getName(), fromEnv);
            String deployParamJson = JSON.toJSONString(po);
            resourceNew.setDeployParam(deployParamJson);
            resourceNew.setModifyDeployParam(true);

            // buildParam
            String codeType = YamlBusinessService.getCodeTypeByProjectName(project.getName(), fromEnv);
            String buildParamJson = YamlBusinessService.getBuildParamJsonStr(codeType, project.getName(), fromEnv);
            resourceNew.setBuildParam(buildParamJson);
            resourceNew.setModifyBuildParam(true);

            // k8s实例
            DpK8sInstanceDto k8sInstanceDto = dpK8sInstanceService.findFirstByEnv(env);
            if (k8sInstanceDto.getId() != null) {
                resourceNew.setK8sInstanceId(k8sInstanceDto.getId());
                resourceNew.setModifyK8sInstanceId(true);
                resourceNew.setK8sNameSpace("cicd-" + fromEnv);
                resourceNew.setModifyK8sNameSpace(true);
            }

            dpServerConfigService.saveOrUpdate(resourceNew);
        } else {
            log.info("DpServerConfigDtoIsExist. projectId:{}, projectName:{},env:{}", project.getId(), project.getName(), env);
        }
    }

    /**
     * 处理某个项目在某个环境下的某些环境变量。
     * 目前只需要处理项目类型是tomcat的javaOpt这个变量
     *
     * @param project
     * @param env
     */
    public void processDpEnvVariablesDtoWithEnv(Project project, String env) {
        String codeType = YamlBusinessService.getCodeTypeByProjectName(project.getName(), env);
        // 如果不是tomcat项目，那么立即结束
        if (!"tomcat".equals(codeType)) {
            return;
        }

        String paramName = "JAVA_OPTS";

        // 如果已经存在，那么不需要处理
        DpEnvVariablesDto dpEnvVariablesDto = dpEnvVariablesService.findFirstByProjectIdAndEnvAndName(project.getId(), env, paramName);
        if (dpEnvVariablesDto != null && dpEnvVariablesDto.getId() != null) {
            log.info("dpEnvVariablesDto has exist. Do not need to sync.");
            return;
        }
        // 不存在的情况
        String javaOptValue = YamlBusinessService.getJavaOptValue(project.getName(), env);
        if (StringUtils.isBlank(javaOptValue)) {
            // 一般不会出现这种情况：如果是tomcat项目，一定要拿到javaOpt这个值！
            log.error("javaOptValueIsNull! projectName:{}, env:{}", project.getName(), env);
        } else {
            DpEnvVariables dpEnvVariables = new DpEnvVariables();
            dpEnvVariables.setEnv(env);
            dpEnvVariables.setProjectId(project.getId());
            dpEnvVariables.setProjectName(project.getName());
            dpEnvVariables.setName(paramName);
            // 0-全局 1-某个服务
            dpEnvVariables.setScope(1);
            dpEnvVariables.setValue(javaOptValue);
            dpEnvVariables.setCreateBy("sync");
            dpEnvVariables.setCreateTime(new Timestamp(System.currentTimeMillis()));
            dpEnvVariables.setUpdateBy("sync");
            dpEnvVariables.setUpdateTime(new Timestamp(System.currentTimeMillis()));
            dpEnvVariablesService.create(dpEnvVariables);
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> updateAvatar(MultipartFile multipartFile) {
        User user = userRepository.findByUsername(SecurityUtils.getCurrentUsername());
        String oldPath = user.getAvatarPath();
        File file = FileUtil.upload(multipartFile, properties.getPath().getAvatar());
        user.setAvatarPath(Objects.requireNonNull(file).getPath());
        user.setAvatarName(file.getName());
        userRepository.save(user);
        if (StringUtils.isNotBlank(oldPath)) {
            FileUtil.del(oldPath);
        }
        @NotBlank String username = user.getUsername();
        flushCache(username);
        return new HashMap<String, String>(1) {{
            put("avatar", file.getName());
        }};
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateEmail(String username, String email) {
        userRepository.updateEmail(username, email);
        flushCache(username);
    }

    @Override
    public void download(List<UserDto> queryAll, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (UserDto userDTO : queryAll) {
            List<String> roles = userDTO.getRoles().stream().map(RoleSmallDto::getName).collect(Collectors.toList());
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("用户名", userDTO.getUsername());
            map.put("角色", roles);
            map.put("部门", userDTO.getDept().getName());
            map.put("岗位", userDTO.getJobs().stream().map(JobSmallDto::getName).collect(Collectors.toList()));
            map.put("邮箱", userDTO.getEmail());
            map.put("状态", userDTO.getEnabled() ? "启用" : "禁用");
            map.put("手机号码", userDTO.getPhone());
            map.put("修改密码的时间", userDTO.getPwdResetTime());
            map.put("创建日期", userDTO.getCreateTime());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    /**
     * 清理缓存
     *
     * @param id /
     */
    public void delCaches(Long id, String username) {
        redisUtils.del(CacheKey.USER_ID + id);
        flushCache(username);
    }

    /**
     * 清理 登陆时 用户缓存信息
     *
     * @param username /
     */
    private void flushCache(String username) {
        userCacheClean.cleanUserCache(username);
    }

    @Override
    public String getUserNameById(Long userId) {
        if (userId == null) {
            return null;
        }
        UserDto userDto = findById(userId);
        if (userDto == null) {
            return null;
        }
        return userDto.getNickName();
    }
}
